/***
 * openapi of indexea
 */
import { v4 as uuidv4 } from 'uuid';
import { Configuration, WidgetsApi, SearchApi, SearchWidgetHotWordsScopeEnum, WidgetBean, SearchWord, AutoCompleteItem } from '@indexea/sdk'

/**
 * A simple wrapper for the Indexea API
 */
export class Indexea {

    _config: Configuration;
    _widget: string;
    searchApi: SearchApi
    widgetApi: WidgetsApi

    /**
     * initialize an indexea api wrapper
     * @param endpoint remote endpoint url, ex: `https://api.indexea.com/v1`
     * @param widget widget identifier
     */
    constructor(endpoint: string, accessToken: string, widget: string) {
        this._widget = widget;
        this._config = new Configuration({
            basePath: endpoint,
            headers: { accept: 'application/json' },
            accessToken: () => {
                return accessToken
            },
            credentials: 'include',
            middleware: [],
            queryParamsStringify
        });
        this.searchApi = new SearchApi(this._config)
        this.widgetApi = new WidgetsApi(this._config)
    }

    /**
     * get widget detail
     * @returns 
     */
    async widget(): Promise<WidgetBean> {
        return this.widgetApi.widgetDetail({ ident: this._widget }).catch(rejectError)
    }

    /**
     * execute search on widget
     * @param query query id
     * @param q   search keyword
     * @param params  search parameters, generally, it is an aggregation parameter filter condition
     * @param from 
     * @param size 
     * @returns 
     */
    async search(query: number, q: string, params: any, from: number, size: number): Promise<object> {
        let args = { widget: this._widget, query, q, params, from, size, userid: getUserId() };
        return this.searchApi.searchWidgetSearch(args).catch(rejectError);
    }

    /**
     * get hotwords of query
     * @param query  query id
     * @param scope 
     * @param count 
     * @returns 
     */
    async hotwords(query: number, scope: SearchWidgetHotWordsScopeEnum, count: number): Promise<Array<SearchWord>> {
        return this.searchApi
            .searchWidgetHotWords({ widget: this._widget, query, scope, count })
            .catch(rejectError)
    }

    /**
     * get auto complete items for search box
     * @param query  query id
     * @param q  search keyword
     * @param size 
     * @returns 
     */
    async autocomplete(query: number, q: string, size: number): Promise<Array<AutoCompleteItem>> {
        return this.searchApi.searchWidgetAutoComplete({ widget: this._widget, query, q, userid: getUserId(), size })
            .catch(rejectError)
    }

    /**
     * call this method when use click on a search result item.
     * @param actionId 
     * @param docId 
     * @returns 
     */
    async click(actionId: string, docId: string): Promise<boolean> {
        return this.searchApi.searchClick({ widget: this._widget, actionId, docId, userid: getUserId() })
    }
}

/**
 * The unique identifier of the visitor, which will be transmitted to the search service through the header
 * @returns 
 */
function getUserId() {
    var userid: string = window.localStorage.getItem('idx_userid') || '';
    if (!userid) {
        userid = uuidv4(); //Date.now().toString(36) + Math.random().toString(36);
        window.localStorage.setItem('idx_userid', userid);
    }
    return userid;
}

/**
 * Object to url request parameters
 * @param obj 
 * @returns 
 */
function queryParamsStringify(obj: Record<string, any>): string {
    return Object.entries(obj).filter(([key, value]) => key && value != undefined)
        .map(([key, value]) => {
            if (typeof value !== 'object') {
                return joinKV(key, value)
            }
            if (Array.isArray(value)) {
                // Array type, using multiple keys, such as a=1&a=2&a=3 becomes a=[1,2,3] during backend processing
                return value.filter(v => v).map(v => joinKV(key, v)).join('&')
            }
            return queryParamsStringify(value)
        })
        .join('&')
}

/**
 * Concatenate url request parameters
 * @param key 
 * @param value 
 * @returns 
 */
function joinKV(key: string, value: any) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}

/**
 * global error handler
 * @param {*} fn_reject
 * @param {*} err
 */
function rejectError(err: any) {
    try {
        return err.response
            .json()
            .then((json: any) => Promise.reject(json))
            .catch((e: { error: any; message: any; }) =>
                Promise.reject({
                    error: e.error || err.status || 999,
                    message: e.message || err.statusText || 'Unknown error'
                })
            )
    } catch (e) {
        return Promise.reject({
            error: err.error || err.status || 999,
            message: err.message || err.statusText || 'Unknown error'
        })
    }
}
